﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cqrs.Infrastructure.Database;
using Raven;
using Raven.Client.Embedded;
using Cqrs.Infrastructure;
using System.IO;
using Raven.Client.Indexes;

namespace EventStore
{
    public class RavenDb : IEventStore
    {
        //private string _dataDir = "";
        private readonly IEventPublisher _publisher;
        private EmbeddableDocumentStore _documentStore;
        public IEventPublisher Publisher
        {
            get
            {
                return _publisher;
            }
        }

        public RavenDb(string dbPath, IEventPublisher publisher, bool createDbFolder = true)
        {
            if (!createDbFolder)
                if (!Directory.Exists(dbPath))
                    throw new ArgumentException(string.Format("The database location {0} does not exist", dbPath));

            _publisher = publisher;
            _documentStore = new EmbeddableDocumentStore()
            {
                DataDirectory = dbPath
            };
            _documentStore.Conventions.DefaultQueryingConsistency = 
                Raven.Client.Document.ConsistencyOptions.QueryYourWrites;
        }

        public void SaveEvents(Guid aggregateId, IEnumerable<Cqrs.Infrastructure.Event> events, int expectedVersion, Type aggregateRootType)
        {
            _documentStore.Initialize();

            using (var session = _documentStore.OpenSession())
            {

                int latestEventVersion = GetLatestEventVersion(aggregateId);

                if (latestEventVersion != expectedVersion && expectedVersion != -1)
                {
                    throw new ConcurrencyException();
                }
                var i = expectedVersion;

                foreach (Cqrs.Infrastructure.Event @event in events)
                {
                    i++;
                    @event.Version = i;
                    EventDescriptor doc = new EventDescriptor(
                        aggregateId, Event.ConvertEventToJson(@event), i, @event.GetType().AssemblyQualifiedName, aggregateRootType.AssemblyQualifiedName);
                    AggregateRootModel aggRootModel = new AggregateRootModel()
                    {
                        Id = aggregateId,
                        Type = aggregateRootType.AssemblyQualifiedName
                    };
                    session.Store(doc);
                    session.Store(aggRootModel);
                    session.SaveChanges();
                    _publisher.Publish(@event);
                }
            }


        }

        private int GetLatestEventVersion(Guid aggregateId)
        {
            _documentStore.Initialize();
            using (var session = _documentStore.OpenSession())
            {
                var events = session.Query<EventDescriptor>()
                    .Where(p => p.AggregateId == aggregateId).ToList();

                if (events.Count == 0)
                    return 0;
                else
                    return events.Max(p => p.Version);
            }

        }

        public List<Cqrs.Infrastructure.Event> GetEventsForAggregate(Guid aggregateId, System.Reflection.Assembly assembly)
        {
            if (aggregateId == Guid.Empty) return null;
            List<Event> events = new List<Event>();
            _documentStore.Initialize();
            using (var session = _documentStore.OpenSession())
            {

                var EventDescriptors = session.Query<EventDescriptor>()
                    .Where(p => p.AggregateId == aggregateId)
                    .ToList()
                    .OrderBy(p => p.Version);

                foreach (var evtDesc in EventDescriptors)
                {

                    Type eventType;
                    if (assembly != null)
                        eventType = assembly.GetTypes().Where(
                            p => p.AssemblyQualifiedName == evtDesc.EventType).First();
                    else
                        eventType = Type.GetType(evtDesc.EventType);

                    if (eventType == null)
                        eventType = typeof(Event);

                    events.Add(Event.ConvertJsonToEvent(evtDesc.EventJson, eventType));
                }
                return events;
            }

        }

        public List<AggregateRootModel> GetAggregateRoots(Type AggregrateRootType)
        {
            _documentStore.Initialize();
            using (var session = _documentStore.OpenSession())
            {
                var aggregateRoots = session.Query<AggregateRootModel>()
                    .Where(p => p.Type == AggregrateRootType.AssemblyQualifiedName).ToList();
                return aggregateRoots;
            }

        }

    }
}
